home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 44 / PC Actual CD 44.iso / Linux / Cygwin / full.exe / Disk1 / data1.cab / Tools / share / tcl8.0 / opt0.1 / optparse.tcl next >
Encoding:
Text File  |  1998-12-04  |  32.6 KB  |  1,095 lines

  1. # optparse.tcl --
  2. #
  3. #       (Private) option parsing package
  4. #
  5. #       This might be documented and exported in 8.1
  6. #       and some function hopefully moved to the C core for
  7. #       efficiency, if there is enough demand. (mail! ;-)
  8. #
  9. #  Author:    Laurent Demailly  - Laurent.Demailly@sun.com - dl@mail.box.eu.org
  10. #
  11. #  Credits:
  12. #             this is a complete 'over kill' rewrite by me, from a version
  13. #             written initially with Brent Welch, itself initially
  14. #             based on work with Steve Uhler. Thanks them !
  15. #
  16. # SCCS: @(#) optparse.tcl 1.13 97/08/21 11:50:42
  17.  
  18. package provide opt 0.2
  19.  
  20. namespace eval ::tcl {
  21.  
  22.     # Exported APIs
  23.     namespace export OptKeyRegister OptKeyDelete OptKeyError OptKeyParse \
  24.              OptProc OptProcArgGiven OptParse \
  25.              Lassign Lvarpop Lvarset Lvarincr Lfirst \
  26.              SetMax SetMin
  27.  
  28.  
  29. #################  Example of use / 'user documentation'  ###################
  30.  
  31.     proc OptCreateTestProc {} {
  32.  
  33.     # Defines ::tcl::OptParseTest as a test proc with parsed arguments
  34.     # (can't be defined before the code below is loaded (before "OptProc"))
  35.  
  36.     # Every OptProc give usage information on "procname -help".
  37.     # Try "tcl::OptParseTest -help" and "tcl::OptParseTest -a" and
  38.     # then other arguments.
  39.     # 
  40.     # example of 'valid' call:
  41.     # ::tcl::OptParseTest save -4 -pr 23 -libsok SybTcl\
  42.     #        -nostatics false ch1
  43.     OptProc OptParseTest {
  44.             {subcommand -choice {save print} "sub command"}
  45.             {arg1 3 "some number"}
  46.             {-aflag}
  47.             {-intflag      7}
  48.             {-weirdflag                    "help string"}
  49.             {-noStatics                    "Not ok to load static packages"}
  50.             {-nestedloading1 true           "OK to load into nested slaves"}
  51.             {-nestedloading2 -boolean true "OK to load into nested slaves"}
  52.             {-libsOK        -choice {Tk SybTcl}
  53.                               "List of packages that can be loaded"}
  54.             {-precision     -int 12        "Number of digits of precision"}
  55.             {-intval        7               "An integer"}
  56.             {-scale         -float 1.0     "Scale factor"}
  57.             {-zoom          1.0             "Zoom factor"}
  58.             {-arbitrary     foobar          "Arbitrary string"}
  59.             {-random        -string 12   "Random string"}
  60.             {-listval       -list {}       "List value"}
  61.             {-blahflag       -blah abc       "Funny type"}
  62.         {arg2 -boolean "a boolean"}
  63.         {arg3 -choice "ch1 ch2"}
  64.         {?optarg? -list {} "optional argument"}
  65.         } {
  66.         foreach v [info locals] {
  67.         puts stderr [format "%14s : %s" $v [set $v]]
  68.         }
  69.     }
  70.     }
  71.  
  72. ###################  No User serviceable part below ! ###############
  73. # You should really not look any further :
  74. # The following is private unexported undocumented unblessed... code 
  75. # time to hit "q" ;-) !
  76.  
  77. # Hmmm... ok, you really want to know ?
  78.  
  79. # You've been warned... Here it is...
  80.  
  81.     # Array storing the parsed descriptions
  82.     variable OptDesc;
  83.     array set OptDesc {};
  84.     # Next potentially free key id (numeric)
  85.     variable OptDescN 0;
  86.  
  87. # Inside algorithm/mechanism description:
  88. # (not for the faint hearted ;-)
  89. #
  90. # The argument description is parsed into a "program tree"
  91. # It is called a "program" because it is the program used by
  92. # the state machine interpreter that use that program to
  93. # actually parse the arguments at run time.
  94. #
  95. # The general structure of a "program" is
  96. # notation (pseudo bnf like)
  97. #    name :== definition        defines "name" as being "definition" 
  98. #    { x y z }                  means list of x, y, and z  
  99. #    x*                         means x repeated 0 or more time
  100. #    x+                         means "x x*"
  101. #    x?                         means optionally x
  102. #    x | y                      means x or y
  103. #    "cccc"                     means the literal string
  104. #
  105. #    program        :== { programCounter programStep* }
  106. #
  107. #    programStep    :== program | singleStep
  108. #
  109. #    programCounter :== {"P" integer+ }
  110. #
  111. #    singleStep     :== { instruction parameters* }
  112. #
  113. #    instruction    :== single element list
  114. #
  115. # (the difference between singleStep and program is that \
  116. #   llength [Lfirst $program] >= 2
  117. # while
  118. #   llength [Lfirst $singleStep] == 1
  119. # )
  120. #
  121. # And for this application:
  122. #
  123. #    singleStep     :== { instruction varname {hasBeenSet currentValue} type 
  124. #                         typeArgs help }
  125. #    instruction    :== "flags" | "value"
  126. #    type           :== knowType | anyword
  127. #    knowType       :== "string" | "int" | "boolean" | "boolflag" | "float"
  128. #                       | "choice"
  129. #
  130. # for type "choice" typeArgs is a list of possible choices, the first one
  131. # is the default value. for all other types the typeArgs is the default value
  132. #
  133. # a "boolflag" is the type for a flag whose presence or absence, without
  134. # additional arguments means respectively true or false (default flag type).
  135. #
  136. # programCounter is the index in the list of the currently processed
  137. # programStep (thus starting at 1 (0 is {"P" prgCounterValue}).
  138. # If it is a list it points toward each currently selected programStep.
  139. # (like for "flags", as they are optional, form a set and programStep).
  140.  
  141. # Performance/Implementation issues
  142. # ---------------------------------
  143. # We use tcl lists instead of arrays because with tcl8.0
  144. # they should start to be much faster.
  145. # But this code use a lot of helper procs (like Lvarset)
  146. # which are quite slow and would be helpfully optimized
  147. # for instance by being written in C. Also our struture
  148. # is complex and there is maybe some places where the
  149. # string rep might be calculated at great exense. to be checked.
  150.  
  151. #
  152. # Parse a given description and saves it here under the given key
  153. # generate a unused keyid if not given
  154. #
  155. proc ::tcl::OptKeyRegister {desc {key ""}} {
  156.     variable OptDesc;
  157.     variable OptDescN;
  158.     if {[string compare $key ""] == 0} {
  159.         # in case a key given to us as a parameter was a number
  160.         while {[info exists OptDesc($OptDescN)]} {incr OptDescN}
  161.         set key $OptDescN;
  162.         incr OptDescN;
  163.     }
  164.     # program counter
  165.     set program [list [list "P" 1]];
  166.  
  167.     # are we processing flags (which makes a single program step)
  168.     set inflags 0;
  169.  
  170.     set state {};
  171.  
  172.     # flag used to detect that we just have a single (flags set) subprogram.
  173.     set empty 1;
  174.  
  175.     foreach item $desc {
  176.     if {$state == "args"} {
  177.         # more items after 'args'...
  178.         return -code error "'args' special argument must be the last one";
  179.     }
  180.         set res [OptNormalizeOne $item];
  181.         set state [Lfirst $res];
  182.         if {$inflags} {
  183.             if {$state == "flags"} {
  184.         # add to 'subprogram'
  185.                 lappend flagsprg $res;
  186.             } else {
  187.                 # put in the flags
  188.                 # structure for flag programs items is a list of
  189.                 # {subprgcounter {prg flag 1} {prg flag 2} {...}}
  190.                 lappend program $flagsprg;
  191.                 # put the other regular stuff
  192.                 lappend program $res;
  193.         set inflags 0;
  194.         set empty 0;
  195.             }
  196.         } else {
  197.            if {$state == "flags"} {
  198.                set inflags 1;
  199.                # sub program counter + first sub program
  200.                set flagsprg [list [list "P" 1] $res];
  201.            } else {
  202.                lappend program $res;
  203.                set empty 0;
  204.            }
  205.        }
  206.    }
  207.    if {$inflags} {
  208.        if {$empty} {
  209.        # We just have the subprogram, optimize and remove
  210.        # unneeded level:
  211.        set program $flagsprg;
  212.        } else {
  213.        lappend program $flagsprg;
  214.        }
  215.    }
  216.  
  217.    set OptDesc($key) $program;
  218.  
  219.    return $key;
  220. }
  221.  
  222. #
  223. # Free the storage for that given key
  224. #
  225. proc ::tcl::OptKeyDelete {key} {
  226.     variable OptDesc;
  227.     unset OptDesc($key);
  228. }
  229.  
  230.     # Get the parsed description stored under the given key.
  231.     proc OptKeyGetDesc {descKey} {
  232.         variable OptDesc;
  233.         if {![info exists OptDesc($descKey)]} {
  234.             return -code error "Unknown option description key \"$descKey\"";
  235.         }
  236.         set OptDesc($descKey);
  237.     }
  238.  
  239. # Parse entry point for ppl who don't want to register with a key,
  240. # for instance because the description changes dynamically.
  241. #  (otherwise one should really use OptKeyRegister once + OptKeyParse
  242. #   as it is way faster or simply OptProc which does it all)
  243. # Assign a temporary key, call OptKeyParse and then free the storage
  244. proc ::tcl::OptParse {desc arglist} {
  245.     set tempkey [OptKeyRegister $desc];
  246.     set ret [catch {uplevel [list ::tcl::OptKeyParse $tempkey $arglist]} res];
  247.     OptKeyDelete $tempkey;
  248.     return -code $ret $res;
  249. }
  250.  
  251. # Helper function, replacement for proc that both
  252. # register the description under a key which is the name of the proc
  253. # (and thus unique to that code)
  254. # and add a first line to the code to call the OptKeyParse proc
  255. # Stores the list of variables that have been actually given by the user
  256. # (the other will be sets to their default value)
  257. # into local variable named "Args".
  258. proc ::tcl::OptProc {name desc body} {
  259.     set namespace [uplevel namespace current];
  260.     if {   ([string match $name "::*"]) 
  261.         || ([string compare $namespace "::"]==0)} {
  262.         # absolute name or global namespace, name is the key
  263.         set key $name;
  264.     } else {
  265.         # we are relative to some non top level namespace:
  266.         set key "${namespace}::${name}";
  267.     }
  268.     OptKeyRegister $desc $key;
  269.     uplevel [list proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"];
  270.     return $key;
  271. }
  272. # Check that a argument has been given
  273. # assumes that "OptProc" has been used as it will check in "Args" list
  274. proc ::tcl::OptProcArgGiven {argname} {
  275.     upvar Args alist;
  276.     expr {[lsearch $alist $argname] >=0}
  277. }
  278.  
  279.     #######
  280.     # Programs/Descriptions manipulation
  281.  
  282.     # Return the instruction word/list of a given step/(sub)program
  283.     proc OptInstr {lst} {
  284.     Lfirst $lst;
  285.     }
  286.     # Is a (sub) program or a plain instruction ?
  287.     proc OptIsPrg {lst} {
  288.     expr {[llength [OptInstr $lst]]>=2}
  289.     }
  290.     # Is this instruction a program counter or a real instr
  291.     proc OptIsCounter {item} {
  292.     expr {[Lfirst $item]=="P"}
  293.     }
  294.     # Current program counter (2nd word of first word)
  295.     proc OptGetPrgCounter {lst} {
  296.     Lget $lst {0 1}
  297.     }
  298.     # Current program counter (2nd word of first word)
  299.     proc OptSetPrgCounter {lstName newValue} {
  300.     upvar $lstName lst;
  301.     set lst [lreplace $lst 0 0 [concat "P" $newValue]];
  302.     }
  303.     # returns a list of currently selected items.
  304.     proc OptSelection {lst} {
  305.     set res {};
  306.     foreach idx [lrange [Lfirst $lst] 1 end] {
  307.         lappend res [Lget $lst $idx];
  308.     }
  309.     return $res;
  310.     }
  311.  
  312.     # Advance to next description
  313.     proc OptNextDesc {descName} {
  314.         uplevel [list Lvarincr $descName {0 1}];
  315.     }
  316.  
  317.     # Get the current description, eventually descend
  318.     proc OptCurDesc {descriptions} {
  319.         lindex $descriptions [OptGetPrgCounter $descriptions];
  320.     }
  321.     # get the current description, eventually descend
  322.     # through sub programs as needed.
  323.     proc OptCurDescFinal {descriptions} {
  324.         set item [OptCurDesc $descriptions];
  325.     # Descend untill we get the actual item and not a sub program
  326.         while {[OptIsPrg $item]} {
  327.             set item [OptCurDesc $item];
  328.         }
  329.     return $item;
  330.     }
  331.     # Current final instruction adress
  332.     proc OptCurAddr {descriptions {start {}}} {
  333.     set adress [OptGetPrgCounter $descriptions];
  334.     lappend start $adress;
  335.     set item [lindex $descriptions $adress];
  336.     if {[OptIsPrg $item]} {
  337.         return [OptCurAddr $item $start];
  338.     } else {
  339.         return $start;
  340.     }
  341.     }
  342.     # Set the value field of the current instruction
  343.     proc OptCurSetValue {descriptionsName value} {
  344.     upvar $descriptionsName descriptions
  345.     # get the current item full adress
  346.         set adress [OptCurAddr $descriptions];
  347.     # use the 3th field of the item  (see OptValue / OptNewInst)
  348.     lappend adress 2
  349.     Lvarset descriptions $adress [list 1 $value];
  350.     #                                  ^hasBeenSet flag
  351.     }
  352.  
  353.     # empty state means done/paste the end of the program
  354.     proc OptState {item} {
  355.         Lfirst $item
  356.     }
  357.     
  358.     # current state
  359.     proc OptCurState {descriptions} {
  360.         OptState [OptCurDesc $descriptions];
  361.     }
  362.  
  363.     #######
  364.     # Arguments manipulation
  365.  
  366.     # Returns the argument that has to be processed now
  367.     proc OptCurrentArg {lst} {
  368.         Lfirst $lst;
  369.     }
  370.     # Advance to next argument
  371.     proc OptNextArg {argsName} {
  372.         uplevel [list Lvarpop $argsName];
  373.     }
  374.     #######
  375.  
  376.  
  377.  
  378.  
  379.  
  380.     # Loop over all descriptions, calling OptDoOne which will
  381.     # eventually eat all the arguments.
  382.     proc OptDoAll {descriptionsName argumentsName} {
  383.     upvar $descriptionsName descriptions
  384.     upvar $argumentsName arguments;
  385. #    puts "entered DoAll";
  386.     # Nb: the places where "state" can be set are tricky to figure
  387.     #     because DoOne sets the state to flagsValue and return -continue
  388.     #     when needed...
  389.     set state [OptCurState $descriptions];
  390.     # We'll exit the loop in "OptDoOne" or when state is empty.
  391.         while 1 {
  392.         set curitem [OptCurDesc $descriptions];
  393.         # Do subprograms if needed, call ourselves on the sub branch
  394.         while {[OptIsPrg $curitem]} {
  395.         OptDoAll curitem arguments
  396. #        puts "done DoAll sub";
  397.         # Insert back the results in current tree;
  398.         Lvarset1nc descriptions [OptGetPrgCounter $descriptions]\
  399.             $curitem;
  400.         OptNextDesc descriptions;
  401.         set curitem [OptCurDesc $descriptions];
  402.                 set state [OptCurState $descriptions];
  403.         }
  404. #           puts "state = \"$state\" - arguments=($arguments)";
  405.         if {[Lempty $state]} {
  406.         # Nothing left to do, we are done in this branch:
  407.         break;
  408.         }
  409.         # The following statement can make us terminate/continue
  410.         # as it use return -code {break, continue, return and error}
  411.         # codes
  412.             OptDoOne descriptions state arguments;
  413.         # If we are here, no special return code where issued,
  414.         # we'll step to next instruction :
  415. #           puts "new state  = \"$state\"";
  416.         OptNextDesc descriptions;
  417.         set state [OptCurState $descriptions];
  418.         }
  419.         if  {![Lempty $arguments]} {
  420.             return -code error [OptTooManyArgs $descriptions $arguments];
  421.         }
  422.     }
  423.  
  424.     # Process one step for the state machine,
  425.     # eventually consuming the current argument.
  426.     proc OptDoOne {descriptionsName stateName argumentsName} {
  427.         upvar $argumentsName arguments;
  428.         upvar $descriptionsName descriptions;
  429.     upvar $stateName state;
  430.  
  431.     # the special state/instruction "args" eats all
  432.     # the remaining args (if any)
  433.     if {($state == "args")} {
  434.         OptCurSetValue descriptions $arguments;
  435.         set arguments {};
  436. #            puts "breaking out ('args' state: consuming every reminding args)"
  437.         return -code break;
  438.     }
  439.  
  440.     if {[Lempty $arguments]} {
  441.         if {$state == "flags"} {
  442.         # no argument and no flags : we're done
  443. #                puts "returning to previous (sub)prg (no more args)";
  444.         return -code return;
  445.         } elseif {$state == "optValue"} {
  446.         set state next; # not used, for debug only
  447.         # go to next state
  448.         return ;
  449.         } else {
  450.         return -code error [OptMissingValue $descriptions];
  451.         }
  452.     } else {
  453.         set arg [OptCurrentArg $arguments];
  454.     }
  455.  
  456.         switch $state {
  457.             flags {
  458.                 # A non-dash argument terminates the options, as does --
  459.  
  460.                 # Still a flag ?
  461.                 if {![OptIsFlag $arg]} {
  462.                     # don't consume the argument, return to previous prg
  463.                     return -code return;
  464.                 }
  465.                 # consume the flag
  466.                 OptNextArg arguments;
  467.                 if {[string compare "--" $arg] == 0} {
  468.                     # return from 'flags' state
  469.                     return -code return;
  470.                 }
  471.  
  472.                 set hits [OptHits descriptions $arg];
  473.                 if {$hits > 1} {
  474.                     return -code error [OptAmbigous $descriptions $arg]
  475.                 } elseif {$hits == 0} {
  476.                     return -code error [OptFlagUsage $descriptions $arg]
  477.                 }
  478.         set item [OptCurDesc $descriptions];
  479.                 if {[OptNeedValue $item]} {
  480.             # we need a value, next state is
  481.             set state flagValue;
  482.                 } else {
  483.                     OptCurSetValue descriptions 1;
  484.                 }
  485.         # continue
  486.         return -code continue;
  487.             }
  488.         flagValue -
  489.         value {
  490.         set item [OptCurDesc $descriptions];
  491.                 # Test the values against their required type
  492.         if [catch {OptCheckType $arg\
  493.             [OptType $item] [OptTypeArgs $item]} val] {
  494.             return -code error [OptBadValue $item $arg $val]
  495.         }
  496.                 # consume the value
  497.                 OptNextArg arguments;
  498.         # set the value
  499.         OptCurSetValue descriptions $val;
  500.         # go to next state
  501.         if {$state == "flagValue"} {
  502.             set state flags
  503.             return -code continue;
  504.         } else {
  505.             set state next; # not used, for debug only
  506.             return ; # will go on next step
  507.         }
  508.         }
  509.         optValue {
  510.         set item [OptCurDesc $descriptions];
  511.                 # Test the values against their required type
  512.         if ![catch {OptCheckType $arg\
  513.             [OptType $item] [OptTypeArgs $item]} val] {
  514.             # right type, so :
  515.             # consume the value
  516.             OptNextArg arguments;
  517.             # set the value
  518.             OptCurSetValue descriptions $val;
  519.         }
  520.         # go to next state
  521.         set state next; # not used, for debug only
  522.         return ; # will go on next step
  523.         }
  524.         }
  525.     # If we reach this point: an unknown
  526.     # state as been entered !
  527.     return -code error "Bug! unknown state in DoOne \"$state\"\
  528.         (prg counter [OptGetPrgCounter $descriptions]:\
  529.             [OptCurDesc $descriptions])";
  530.     }
  531.  
  532. # Parse the options given the key to previously registered description
  533. # and arguments list
  534. proc ::tcl::OptKeyParse {descKey arglist} {
  535.  
  536.     set desc [OptKeyGetDesc $descKey];
  537.  
  538.     # make sure -help always give usage
  539.     if {[string compare "-help" [string tolower $arglist]] == 0} {
  540.     return -code error [OptError "Usage information:" $desc 1];
  541.     }
  542.  
  543.     OptDoAll desc arglist;
  544.     
  545.     # Analyse the result
  546.     # Walk through the tree:
  547.     OptTreeVars $desc "#[expr [info level]-1]" ;
  548. }
  549.  
  550.     # determine string length for nice tabulated output
  551.     proc OptTreeVars {desc level {vnamesLst {}}} {
  552.     foreach item $desc {
  553.         if {[OptIsCounter $item]} continue;
  554.         if {[OptIsPrg $item]} {
  555.         set vnamesLst [OptTreeVars $item $level $vnamesLst];
  556.         } else {
  557.         set vname [OptVarName $item];
  558.         upvar $level $vname var
  559.         if {[OptHasBeenSet $item]} {
  560. #            puts "adding $vname"
  561.             # lets use the input name for the returned list
  562.             # it is more usefull, for instance you can check that
  563.             # no flags at all was given with expr
  564.             # {![string match "*-*" $Args]}
  565.             lappend vnamesLst [OptName $item];
  566.             set var [OptValue $item];
  567.         } else {
  568.             set var [OptDefaultValue $item];
  569.         }
  570.         }
  571.     }
  572.     return $vnamesLst
  573.     }
  574.  
  575.  
  576. # Check the type of a value
  577. # and emit an error if arg is not of the correct type
  578. # otherwise returns the canonical value of that arg (ie 0/1 for booleans)
  579. proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
  580. #    puts "checking '$arg' against '$type' ($typeArgs)";
  581.  
  582.     # only types "any", "choice", and numbers can have leading "-"
  583.  
  584.     switch -exact -- $type {
  585.         int {
  586.             if ![regexp {^(-+)?[0-9]+$} $arg] {
  587.                 error "not an integer"
  588.             }
  589.         return $arg;
  590.         }
  591.         float {
  592.             return [expr double($arg)]
  593.         }
  594.     script -
  595.         list {
  596.         # if llength fail : malformed list
  597.             if {[llength $arg]==0} {
  598.         if {[OptIsFlag $arg]} {
  599.             error "no values with leading -"
  600.         }
  601.         }
  602.         return $arg;
  603.         }
  604.         boolean {
  605.         if ![regexp -nocase {^(true|false|0|1)$} $arg] {
  606.         error "non canonic boolean"
  607.             }
  608.         # convert true/false because expr/if is broken with "!,...
  609.         if {$arg} {
  610.         return 1
  611.         } else {
  612.         return 0
  613.         }
  614.         }
  615.         choice {
  616.             if {[lsearch -exact $typeArgs $arg] < 0} {
  617.                 error "invalid choice"
  618.             }
  619.         return $arg;
  620.         }
  621.     any {
  622.         return $arg;
  623.     }
  624.     string -
  625.     default {
  626.             if {[OptIsFlag $arg]} {
  627.                 error "no values with leading -"
  628.             }
  629.         return $arg
  630.         }
  631.     }
  632.     return neverReached;
  633. }
  634.  
  635.     # internal utilities
  636.  
  637.     # returns the number of flags matching the given arg
  638.     # sets the (local) prg counter to the list of matches
  639.     proc OptHits {descName arg} {
  640.         upvar $descName desc;
  641.         set hits 0
  642.         set hitems {}
  643.     set i 1;
  644.  
  645.     set larg [string tolower $arg];
  646.     set len  [string length $larg];
  647.     set last [expr $len-1];
  648.  
  649.         foreach item [lrange $desc 1 end] {
  650.             set flag [OptName $item]
  651.         # lets try to match case insensitively
  652.         # (string length ought to be cheap)
  653.         set lflag [string tolower $flag];
  654.         if {$len == [string length $lflag]} {
  655.         if {[string compare $larg $lflag]==0} {
  656.             # Exact match case
  657.             OptSetPrgCounter desc $i;
  658.             return 1;
  659.         }
  660.         } else {
  661.         if {[string compare $larg [string range $lflag 0 $last]]==0} {
  662.             lappend hitems $i;
  663.             incr hits;
  664.         }
  665.             }
  666.         incr i;
  667.         }
  668.     if {$hits} {
  669.         OptSetPrgCounter desc $hitems;
  670.     }
  671.         return $hits
  672.     }
  673.  
  674.     # Extract fields from the list structure:
  675.  
  676.     proc OptName {item} {
  677.         lindex $item 1;
  678.     }
  679.     # 
  680.     proc OptHasBeenSet {item} {
  681.     Lget $item {2 0};
  682.     }
  683.     # 
  684.     proc OptValue {item} {
  685.     Lget $item {2 1};
  686.     }
  687.  
  688.     proc OptIsFlag {name} {
  689.         string match "-*" $name;
  690.     }
  691.     proc OptIsOpt {name} {
  692.         string match {\?*} $name;
  693.     }
  694.     proc OptVarName {item} {
  695.         set name [OptName $item];
  696.         if {[OptIsFlag $name]} {
  697.             return [string range $name 1 end];
  698.         } elseif {[OptIsOpt $name]} {
  699.         return [string trim $name "?"];
  700.     } else {
  701.             return $name;
  702.         }
  703.     }
  704.     proc OptType {item} {
  705.         lindex $item 3
  706.     }
  707.     proc OptTypeArgs {item} {
  708.         lindex $item 4
  709.     }
  710.     proc OptHelp {item} {
  711.         lindex $item 5
  712.     }
  713.     proc OptNeedValue {item} {
  714.         string compare [OptType $item] boolflag
  715.     }
  716.     proc OptDefaultValue {item} {
  717.         set val [OptTypeArgs $item]
  718.         switch -exact -- [OptType $item] {
  719.             choice {return [lindex $val 0]}
  720.         boolean -
  721.         boolflag {
  722.         # convert back false/true to 0/1 because expr !$bool
  723.         # is broken..
  724.         if {$val} {
  725.             return 1
  726.         } else {
  727.             return 0
  728.         }
  729.         }
  730.         }
  731.         return $val
  732.     }
  733.  
  734.     # Description format error helper
  735.     proc OptOptUsage {item {what ""}} {
  736.         return -code error "invalid description format$what: $item\n\
  737.                 should be a list of {varname|-flagname ?-type? ?defaultvalue?\
  738.                 ?helpstring?}";
  739.     }
  740.  
  741.  
  742.     # Generate a canonical form single instruction
  743.     proc OptNewInst {state varname type typeArgs help} {
  744.     list $state $varname [list 0 {}] $type $typeArgs $help;
  745.     #                          ^  ^
  746.     #                          |  |
  747.     #               hasBeenSet=+  +=currentValue
  748.     }
  749.  
  750.     # Translate one item to canonical form
  751.     proc OptNormalizeOne {item} {
  752.         set lg [Lassign $item varname arg1 arg2 arg3];
  753. #       puts "called optnormalizeone '$item' v=($varname), lg=$lg";
  754.         set isflag [OptIsFlag $varname];
  755.     set isopt  [OptIsOpt  $varname];
  756.         if {$isflag} {
  757.             set state "flags";
  758.         } elseif {$isopt} {
  759.         set state "optValue";
  760.     } elseif {[string compare $varname "args"]} {
  761.         set state "value";
  762.     } else {
  763.         set state "args";
  764.     }
  765.  
  766.     # apply 'smart' 'fuzzy' logic to try to make
  767.     # description writer's life easy, and our's difficult :
  768.     # let's guess the missing arguments :-)
  769.  
  770.         switch $lg {
  771.             1 {
  772.                 if {$isflag} {
  773.                     return [OptNewInst $state $varname boolflag false ""];
  774.                 } else {
  775.                     return [OptNewInst $state $varname any "" ""];
  776.                 }
  777.             }
  778.             2 {
  779.                 # varname default
  780.                 # varname help
  781.                 set type [OptGuessType $arg1]
  782.                 if {[string compare $type "string"] == 0} {
  783.                     if {$isflag} {
  784.             set type boolflag
  785.             set def false
  786.             } else {
  787.             set type any
  788.             set def ""
  789.             }
  790.             set help $arg1
  791.                 } else {
  792.                     set help ""
  793.                     set def $arg1
  794.                 }
  795.                 return [OptNewInst $state $varname $type $def $help];
  796.             }
  797.             3 {
  798.                 # varname type value
  799.                 # varname value comment
  800.         
  801.                 if [regexp {^-(.+)$} $arg1 x type] {
  802.             # flags/optValue as they are optional, need a "value",
  803.             # on the contrary, for a variable (non optional),
  804.                 # default value is pointless, 'cept for choices :
  805.             if {$isflag || $isopt || ($type == "choice")} {
  806.             return [OptNewInst $state $varname $type $arg2 ""];
  807.             } else {
  808.             return [OptNewInst $state $varname $type "" $arg2];
  809.             }
  810.                 } else {
  811.                     return [OptNewInst $state $varname\
  812.                 [OptGuessType $arg1] $arg1 $arg2]
  813.                 }
  814.             }
  815.             4 {
  816.                 if [regexp {^-(.+)$} $arg1 x type] {
  817.             return [OptNewInst $state $varname $type $arg2 $arg3];
  818.                 } else {
  819.                     return -code error [OptOptUsage $item];
  820.                 }
  821.             }
  822.             default {
  823.                 return -code error [OptOptUsage $item];
  824.             }
  825.         }
  826.     }
  827.  
  828.     # Auto magic lasy type determination
  829.     proc OptGuessType {arg} {
  830.         if [regexp -nocase {^(true|false)$} $arg] {
  831.             return boolean
  832.         }
  833.         if [regexp {^(-+)?[0-9]+$} $arg] {
  834.             return int
  835.         }
  836.         if ![catch {expr double($arg)}] {
  837.             return float
  838.         }
  839.         return string
  840.     }
  841.  
  842.     # Error messages front ends
  843.  
  844.     proc OptAmbigous {desc arg} {
  845.         OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc]
  846.     }
  847.     proc OptFlagUsage {desc arg} {
  848.         OptError "bad flag \"$arg\", must be one of" $desc;
  849.     }
  850.     proc OptTooManyArgs {desc arguments} {
  851.         OptError "too many arguments (unexpected argument(s): $arguments),\
  852.         usage:"\
  853.         $desc 1
  854.     }
  855.     proc OptParamType {item} {
  856.     if {[OptIsFlag $item]} {
  857.         return "flag";
  858.     } else {
  859.         return "parameter";
  860.     }
  861.     }
  862.     proc OptBadValue {item arg {err {}}} {
  863. #       puts "bad val err = \"$err\"";
  864.         OptError "bad value \"$arg\" for [OptParamType $item]"\
  865.         [list $item]
  866.     }
  867.     proc OptMissingValue {descriptions} {
  868. #        set item [OptCurDescFinal $descriptions];
  869.         set item [OptCurDesc $descriptions];
  870.         OptError "no value given for [OptParamType $item] \"[OptName $item]\"\
  871.         (use -help for full usage) :"\
  872.         [list $item]
  873.     }
  874.  
  875. proc ::tcl::OptKeyError {prefix descKey {header 0}} {
  876.     OptError $prefix [OptKeyGetDesc $descKey] $header;
  877. }
  878.  
  879.     # determine string length for nice tabulated output
  880.     proc OptLengths {desc nlName tlName dlName} {
  881.     upvar $nlName nl;
  882.     upvar $tlName tl;
  883.     upvar $dlName dl;
  884.     foreach item $desc {
  885.         if {[OptIsCounter $item]} continue;
  886.         if {[OptIsPrg $item]} {
  887.         OptLengths $item nl tl dl
  888.         } else {
  889.         SetMax nl [string length [OptName $item]]
  890.         SetMax tl [string length [OptType $item]]
  891.         set dv [OptTypeArgs $item];
  892.         if {[OptState $item] != "header"} {
  893.             set dv "($dv)";
  894.         }
  895.         set l [string length $dv];
  896.         # limit the space allocated to potentially big "choices"
  897.         if {([OptType $item] != "choice") || ($l<=12)} {
  898.             SetMax dl $l
  899.         } else {
  900.             if {![info exists dl]} {
  901.             set dl 0
  902.             }
  903.         }
  904.         }
  905.     }
  906.     }
  907.     # output the tree
  908.     proc OptTree {desc nl tl dl} {
  909.     set res "";
  910.     foreach item $desc {
  911.         if {[OptIsCounter $item]} continue;
  912.         if {[OptIsPrg $item]} {
  913.         append res [OptTree $item $nl $tl $dl];
  914.         } else {
  915.         set dv [OptTypeArgs $item];
  916.         if {[OptState $item] != "header"} {
  917.             set dv "($dv)";
  918.         }
  919.         append res [format "\n    %-*s %-*s %-*s %s" \
  920.             $nl [OptName $item] $tl [OptType $item] \
  921.             $dl $dv [OptHelp $item]]
  922.         }
  923.     }
  924.     return $res;
  925.     }
  926.  
  927. # Give nice usage string
  928. proc ::tcl::OptError {prefix desc {header 0}} {
  929.     # determine length
  930.     if {$header} {
  931.     # add faked instruction
  932.     set h [list [OptNewInst header Var/FlagName Type Value Help]];
  933.     lappend h   [OptNewInst header ------------ ---- ----- ----];
  934.     lappend h   [OptNewInst header {( -help} "" "" {gives this help )}]
  935.     set desc [concat $h $desc]
  936.     }
  937.     OptLengths $desc nl tl dl
  938.     # actually output 
  939.     return "$prefix[OptTree $desc $nl $tl $dl]"
  940. }
  941.  
  942.  
  943. ################     General Utility functions   #######################
  944.  
  945. #
  946. # List utility functions
  947. # Naming convention:
  948. #     "Lvarxxx" take the list VARiable name as argument
  949. #     "Lxxxx"   take the list value as argument
  950. #               (which is not costly with Tcl8 objects system
  951. #                as it's still a reference and not a copy of the values)
  952. #
  953.  
  954. # Is that list empty ?
  955. proc ::tcl::Lempty {list} {
  956.     expr {[llength $list]==0}
  957. }
  958.  
  959. # Gets the value of one leaf of a lists tree
  960. proc ::tcl::Lget {list indexLst} {
  961.     if {[llength $indexLst] <= 1} {
  962.         return [lindex $list $indexLst];
  963.     }
  964.     Lget [lindex $list [Lfirst $indexLst]] [Lrest $indexLst];
  965. }
  966. # Sets the value of one leaf of a lists tree
  967. # (we use the version that does not create the elements because
  968. #  it would be even slower... needs to be written in C !)
  969. # (nb: there is a non trivial recursive problem with indexes 0,
  970. #  which appear because there is no difference between a list
  971. #  of 1 element and 1 element alone : [list "a"] == "a" while 
  972. #  it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1
  973. #  and [listp "a b"] maybe 0. listp does not exist either...)
  974. proc ::tcl::Lvarset {listName indexLst newValue} {
  975.     upvar $listName list;
  976.     if {[llength $indexLst] <= 1} {
  977.         Lvarset1nc list $indexLst $newValue;
  978.     } else {
  979.         set idx [Lfirst $indexLst];
  980.         set targetList [lindex $list $idx];
  981.         # reduce refcount on targetList (not really usefull now,
  982.     # could be with optimizing compiler)
  983. #        Lvarset1 list $idx {};
  984.         # recursively replace in targetList
  985.         Lvarset targetList [Lrest $indexLst] $newValue;
  986.         # put updated sub list back in the tree
  987.         Lvarset1nc list $idx $targetList;
  988.     }
  989. }
  990. # Set one cell to a value, eventually create all the needed elements
  991. # (on level-1 of lists)
  992. variable emptyList {}
  993. proc ::tcl::Lvarset1 {listName index newValue} {
  994.     upvar $listName list;
  995.     if {$index < 0} {return -code error "invalid negative index"}
  996.     set lg [llength $list];
  997.     if {$index >= $lg} {
  998.         variable emptyList;
  999.         for {set i $lg} {$i<$index} {incr i} {
  1000.             lappend list $emptyList;
  1001.         }
  1002.         lappend list $newValue;
  1003.     } else {
  1004.         set list [lreplace $list $index $index $newValue];
  1005.     }
  1006. }
  1007. # same as Lvarset1 but no bound checking / creation
  1008. proc ::tcl::Lvarset1nc {listName index newValue} {
  1009.     upvar $listName list;
  1010.     set list [lreplace $list $index $index $newValue];
  1011. }
  1012. # Increments the value of one leaf of a lists tree
  1013. # (which must exists)
  1014. proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} {
  1015.     upvar $listName list;
  1016.     if {[llength $indexLst] <= 1} {
  1017.         Lvarincr1 list $indexLst $howMuch;
  1018.     } else {
  1019.         set idx [Lfirst $indexLst];
  1020.         set targetList [lindex $list $idx];
  1021.         # reduce refcount on targetList
  1022.         Lvarset1nc list $idx {};
  1023.         # recursively replace in targetList
  1024.         Lvarincr targetList [Lrest $indexLst] $howMuch;
  1025.         # put updated sub list back in the tree
  1026.         Lvarset1nc list $idx $targetList;
  1027.     }
  1028. }
  1029. # Increments the value of one cell of a list
  1030. proc ::tcl::Lvarincr1 {listName index {howMuch 1}} {
  1031.     upvar $listName list;
  1032.     set newValue [expr [lindex $list $index]+$howMuch];
  1033.     set list [lreplace $list $index $index $newValue];
  1034.     return $newValue;
  1035. }
  1036. # Returns the first element of a list
  1037. proc ::tcl::Lfirst {list} {
  1038.     lindex $list 0
  1039. }
  1040. # Returns the rest of the list minus first element
  1041. proc ::tcl::Lrest {list} {
  1042.     lrange $list 1 end
  1043. }
  1044. # Removes the first element of a list
  1045. proc ::tcl::Lvarpop {listName} {
  1046.     upvar $listName list;
  1047.     set list [lrange $list 1 end];
  1048. }
  1049. # Same but returns the removed element
  1050. proc ::tcl::Lvarpop2 {listName} {
  1051.     upvar $listName list;
  1052.     set el [Lfirst $list];
  1053.     set list [lrange $list 1 end];
  1054.     return $el;
  1055. }
  1056. # Assign list elements to variables and return the length of the list
  1057. proc ::tcl::Lassign {list args} {
  1058.     # faster than direct blown foreach (which does not byte compile)
  1059.     set i 0;
  1060.     set lg [llength $list];
  1061.     foreach vname $args {
  1062.         if {$i>=$lg} break
  1063.         uplevel [list set $vname [lindex $list $i]];
  1064.         incr i;
  1065.     }
  1066.     return $lg;
  1067. }
  1068.  
  1069. # Misc utilities
  1070.  
  1071. # Set the varname to value if value is greater than varname's current value
  1072. # or if varname is undefined
  1073. proc ::tcl::SetMax {varname value} {
  1074.     upvar 1 $varname var
  1075.     if {![info exists var] || $value > $var} {
  1076.         set var $value
  1077.     }
  1078. }
  1079.  
  1080. # Set the varname to value if value is smaller than varname's current value
  1081. # or if varname is undefined
  1082. proc ::tcl::SetMin {varname value} {
  1083.     upvar 1 $varname var
  1084.     if {![info exists var] || $value < $var} {
  1085.         set var $value
  1086.     }
  1087. }
  1088.  
  1089.  
  1090.     # everything loaded fine, lets create the test proc:
  1091.     OptCreateTestProc
  1092.     # Don't need the create temp proc anymore:
  1093.     rename OptCreateTestProc {}
  1094. }
  1095.